// import AutoEncryptLocalhost from '@small-tech/auto-encrypt-localhost'
/**
 * 获取本地Ip地址
 *
 * @returns {string[]} 地址列表
 */
function getIps() {
  const os = require('os')
  const _net = Object.values(os.networkInterfaces())
  let temp = []
  _net.forEach((ele1) => {
    ele1.forEach((ele2) => {
      if (ele2.family !== undefined && ele2.family.toUpperCase() === 'IPV4') {
        temp.push(ele2)
      }
    })
  })
  temp = [...new Set(['localhost', ...temp.map(({ address }) => address)])]
  return temp
}
/**
 * @param {string}url 要检查的url
 * @returns {boolean} 该url是否基于本地ip
 */
function isLocalhost(url) {
  const ips = getIps()
  for (let ip of ips) {
    ip = ip.replace(/\./g, '\\.')
    const reg = new RegExp(`^https?://${ip}`, 'i')
    if (reg.test(url)) {
      return true
    }
  }
  return false
}
/**
 * 获取被占用额端口号
 *
 * @returns {Promise<number[]>} 返回端口数字合集
 */
function getOccupiedPorts() {
  const os = require('os')
  const { exec } = require('child_process')
  const command = /windows/i.test(os.type()) ? 'netstat -ano' : 'netstat -tunlp'
  return new Promise((resolve, reject) => {
    // eslint-disable-next-line node/handle-callback-err
    exec(command, (err, stdout) => {
      const regs0 = /\s+\d+(\.\d+){3}:(?<port>\d+)\s+/g
      const regs1 = /\s+(\[::\]|::):(?<port>\d+)\s+/g
      const arr0 = [
        ...(`${stdout}`.match(regs0) || []),
        ...(`${stdout}`.match(regs1) || [])
      ]
        .map((e) => {
          const groups = [...e.split(':')]
          return groups.pop()
        })
        .map((e) => +e)
      const result = [...new Set(arr0)].filter((e) => e)
      resolve(result)
    })
  })
}

/**
 * @param {number}port 想要使用的端口
 * @returns {number}由于占用实际使用的端口
 */
async function getUnOccupiedPort(port) {
  port = +port
  const occupied = await getOccupiedPorts()
  while (occupied.includes(port)) {
    port++
  }
  return port
}
/**
 * @param {object}root0 选项
 * @param {string}root0.atDir 工作目录，当计算出cert之后，会删除整个目录
 * @returns {Promise<object>} 返回mkcrt的tls证书
 */
function createLocalCert({
  atDir = require('path').join(process.cwd(), 'https-crt')
}) {
  const os = require('os')
  const { exec } = require('child_process')
  const Path = require('path')
  const klawSync = require('klaw-sync')
  const Fs = require('fs-extra')
  const ipAddresses = getIps().join(' ')
  let newMkcerExePath = Path.join(atDir, 'mkcert32.exe')
  const originMkcerExePath = Path.join(__dirname, 'mkcert32.exe')
  const isAtOriginalDir = newMkcerExePath === originMkcerExePath
  newMkcerExePath = newMkcerExePath.replace(
    /mkcert32.exe$/,
    `mkcert32${~~(Math.random() * 9999999)}.exe`
  )
  console.log('original mkcert @ ', originMkcerExePath.replace(/.exe$/, ''))
  if (!isAtOriginalDir) {
    Fs.removeSync(atDir)
    Fs.ensureDirSync(atDir)
    Fs.copySync(originMkcerExePath, newMkcerExePath, { overwrite: true })
    Fs.copySync(
      originMkcerExePath.replace(/\.exe$/, ''),
      newMkcerExePath.replace(/\.exe$/, ''),
      {
        overwrite: true
      }
    )
  }
  // eslint-disable-next-line no-async-promise-executor
  return new Promise(async (resolve, reject) => {
    const isWin = /windows/i.test(os.type())
    let cmd0 = `${newMkcerExePath.replace(/\.exe$/, '')} -install`
    if (!isWin) {
      cmd0 = [
        `sudo -s`,
        `apt update`,
        `apt upgrade`,
        `apt install libnss3-tools`,
        `${newMkcerExePath.replace(/\.exe$/, '')} -install`
      ].join(' && ')
    }
    exec(cmd0, { cwd: atDir }, () => {
      exec(
        `${newMkcerExePath} ${ipAddresses}`,
        { cwd: atDir },
        // eslint-disable-next-line node/handle-callback-err
        (err, stdout) => {
          console.log(stdout)
          const httpsOptions = {}
          const files = [...klawSync(atDir, { nodir: true })]
            .map(({ path }) => path)
            .filter((path) => {
              return /\.pem/i.test(Path.extname(path))
            })
          files.forEach((e) => {
            if (/key\.pem/i.test(e)) {
              httpsOptions.key = Fs.readFileSync(e)
            } else {
              httpsOptions.cert = Fs.readFileSync(e)
            }
          })
          if (!isAtOriginalDir) {
            Fs.removeSync(atDir)
          }
          resolve(httpsOptions)
        }
      )
    })
  })
}
/**
 * @param {object}root0 - 选项
 * @param {Function[]}[root0.creations=[]] - ({app, wss, broadcast, httpServer})= > { ...todo }
 * @param {Function[]}[root0.setup=[]] - ({app, wss, broadcast, httpServer})= > { ...todo }
 * @param {number}[root0.httpPort=3000] - http服务器端口
 * @param {number}[root0.httpsPort=3001] - https服务器端口
 * @param {object}root0.httpsOptions  https的key和cert
 //  * @param {Buffer}root0.httpsOptions.key https-key 默认用localhost
 //  * @param {Buffer}root0.httpsOptions.cert https-cert 默认用localhost
 * @param {number}[root0.port=8080] 代理的端口()
 * @param {boolean}root0.CORS 是否跨域
 * @param {boolean}root0.changePortWhenOccupied 是否在端口被占用的时候主动更改端口
 * @returns {Promise<object>}返回{app, httpServer, wss, wsss, broadcast(msg)=>会将msg转为JSON发送给每个client, wsBoth: [wss, wsss]}
 */
async function expressWssServer({
  creations = [],
  setup = [],
  httpPort = 3000,
  httpsPort = 3001,
  httpsOptions,
  port = 8080,
  CORS = true,
  changePortWhenOccupied = true
}) {
  const WebSocket = require('ws')
  const { Server: CreateWSServer } = WebSocket
  const Http = require('http')
  const Https = require('https')
  const Express = require('express')
  const net = require('net')
  const httpsOptionsActual = httpsOptions || (await createLocalCert({}))
  const serverInitialization = {
    addExpress: function ({ app = Express() }) {
      app.use(Express.json({ limit: '100mb' }))
      app.use(Express.urlencoded({ extended: true, limit: '100mb' }))
      Object.assign(arguments[0], { app })
    },
    addHttpServer: function ({
      app = Express(),
      httpServer = Http.createServer(app)
    }) {
      Object.assign(arguments[0], { app, httpServer })
    },
    addWss: function ({
      app = Express(),
      httpServer = Http.createServer(app),
      httpsServer = Https.createServer(httpsOptionsActual, app),
      //   httpsServer = AutoEncryptLocalhost.https.createServer(app),
      wss = new CreateWSServer({ server: httpServer }),
      wsss = new CreateWSServer({ server: httpsServer }),
      broadcast = (data) => {
        wss.clients.forEach(function each(client) {
          if (client.readyState === WebSocket.OPEN) {
            client.send(JSON.stringify(data))
          }
        })
        wsss.clients.forEach(function each(client) {
          if (client.readyState === WebSocket.OPEN) {
            client.send(JSON.stringify(data))
          }
        })
      }
    }) {
      Object.assign(arguments[0], {
        app,
        httpServer,
        httpsServer,
        wss,
        wsss,
        broadcast,
        wsBoth: [wss, wsss]
      })
    }
  }
  const serverInstance = {}
  for (const key in serverInitialization) {
    serverInitialization[key](serverInstance)
  }
  for (const key in setup) {
    const _result = setup[key](serverInstance)
    if (Object.prototype.toString.call(_result) === '[object Promise]') {
      await _result
    }
  }
  for (const key in creations) {
    const _result = creations[key](serverInstance)
    if (Object.prototype.toString.call(_result) === '[object Promise]') {
      await _result
    }
  }
  if (CORS) {
    // 最后设置设置跨域
    serverInstance.app.use((req, res, next) => {
      // Access-Control-Allow-Credentials
      res.set({
        'Access-Control-Allow-Credentials': true,
        'Access-Control-Max-Age': 1728000,
        'Access-Control-Allow-Origin': req.headers.origin || '*',
        'Access-Control-Allow-Headers': 'X-Requested-With,Content-Type',
        'Access-Control-Allow-Methods': 'PUT,POST,GET,DELETE,OPTIONS',
        'Content-Type': 'application/json; charset=utf-8'
      })
      req.method === 'OPTIONS' ? res.status(204).end() : next()
    })
  }
  const { httpServer } = serverInstance
  changePortWhenOccupied && (port = await getUnOccupiedPort(port))

  changePortWhenOccupied && (httpPort = await getUnOccupiedPort(httpPort))
  httpServer.listen(httpPort)

  changePortWhenOccupied && (httpsPort = await getUnOccupiedPort(httpsPort))
  const { httpsServer } = serverInstance
  httpsServer.listen(httpsPort)

  net
    .createServer(function (socket) {
      socket.once('data', function (buf) {
        console.log(buf[0])
        // https数据流的第一位是十六进制“16”，转换成十进制就是22
        const address = buf[0] === 22 ? httpsPort : httpPort
        // 创建一个指向https或http服务器的链接
        const proxy = net.createConnection(address, function () {
          proxy.write(buf)
          // 反向代理的过程，tcp接受的数据交给代理链接，代理链接服务器端返回数据交由socket返回给客户端
          socket.pipe(proxy).pipe(socket)
        })
        proxy.on('error', console.log)
      })
      socket.on('error', console.log)
    })
    .listen(port)
  for (const ip of getIps()) {
    console.log('server listening at:', {
      // https: `https://${ip}:${httpsPort}`,
      // http: `http://${ip}:${httpPort}`,
      proxy: `http://${ip}:${port}`
    })
  }
  return {
    ...arguments[0],
    ...serverInstance,
    httpPort,
    httpsPort,
    proxyPort: port
  }
}
/**
 * @param {object}root0 - 选项
 * @param {Function[]}root0.creations - ({app, wss, broadcast, httpServer})= > { ...todo }
 * @param {number}[root0.httpPort=8080] - http服务器端口
 * @param {number}[root0.httpsPort=8081] - https服务器端口
 * @param {object}root0.httpsOptions  https的key和cert
 //  * @param {Buffer}root0.httpsOptions.key https-key 默认用localhost
 //  * @param {Buffer}root0.httpsOptions.cert https-cert 默认用localhost
 * @param {number}[root0.proxyPort=3344] 反向代理的端口
 * @param {boolean}root0.CORS 是否跨域
 * @returns {Promise<object>}返回{app, httpServer, wss, wsss, broadcast, wsBoth: [wss, wsss]}
 */
async function clusterExpressWssServer(root0) {
  const cluster = require('cluster')
  const numCPUs = require('os').cpus().length
  const process = require('process')
  const { createLocalCert } = require('albertgao.common-electron')
  root0.httpsOptions || (root0.httpsOptions = await createLocalCert({}))
  if (cluster.isPrimary) {
    console.log(`Primary ${process.pid} is running`)
    // 衍生工作进程。
    for (let i = 0; i < numCPUs; i++) {
      cluster.fork()
    }
    cluster.on('exit', (worker, code, signal) => {
      console.log(`worker ${worker.process.pid} died`)
    })
  } else {
    // 工作进程可以共享任何 TCP 连接
    console.log(`Worker ${process.pid} started`)
    return await expressWssServer({ ...root0, changePortWhenOccupied: false })
  }
}

/**
 * @param {WebSocket.Server[]} wssArr ws服务器列表
 * @param {boolean}[onlineOnly=true] 是否只筛选在线的
 * @returns {object[]}返回服务器的所有clients
 */
function getWssClients(wssArr, onlineOnly = true) {
  const WebSocket = require('ws')
  let arr = []
  for (const { clients } of wssArr) {
    arr = [...arr, ...clients]
  }
  if (onlineOnly) {
    return arr.filter(({ readyState }) => readyState === WebSocket.OPEN)
  } else {
    return arr
  }
}
/**
 * 设置Wss服务器
 *
 * @param {object[]}wssArr wss服务器数组
 * @param {Function}broadcast 功能(会将内容转为JSON.stringify)
 * @param {object[]}handlers 处理信息的数组
 * @param {string}[handlers.e="message"] 什么类型的event，默认message
 * @param {Function}[handlers.func=({wss,broadcast,msg,json,clients})=>0] 处理函数，传入三个参数wss, broadcast, msg，其中msg已尝试JSON.parse
 */
function handleWsClient(wssArr = [], broadcast = () => 0, handlers = []) {
  for (const wss of wssArr) {
    wss.on('connection', function connection(ws) {
      for (const { e = 'message', func = () => 0 } of handlers) {
        ws.on(e, (msg) => {
          // console.log('@configwss msg=', { msg })
          try {
            const json = JSON.parse(msg)
            func({ wss, broadcast, json, msg, clients: getWssClients(wssArr) })
          } catch (error) {
            func({ wss, broadcast, msg, clients: getWssClients(wssArr) })
          }
        })
      }
    })
  }
}
module.exports = {
  getIps,
  isLocalhost,
  createLocalCert,
  expressWssServer,
  getOccupiedPorts,
  getUnOccupiedPort,
  clusterExpressWssServer,
  getWssClients,
  handleWsClient
}
